
/**
  ******************************************************************************
  * Copyright 2021 The Microbee Authors. All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * 
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * 
  * @file       mb_socket.c
  * @author     baiyang
  * @date       2022-12-1
  ******************************************************************************
  */

/*----------------------------------include-----------------------------------*/
#include "mb_socket.h"

#include <string.h>

#include <rtthread.h>
/*-----------------------------------macro------------------------------------*/
#if defined(RT_USING_SAL)
/*----------------------------------typedef-----------------------------------*/

/*---------------------------------prototype----------------------------------*/

/*----------------------------------variable----------------------------------*/

/*-------------------------------------os-------------------------------------*/

/*----------------------------------function----------------------------------*/
/*
  constructor
 */
void mb_socket_ctor(mb_socket_t s, bool _datagram)
{
    mb_socket_ctor2(s, _datagram, socket(AF_INET, _datagram?SOCK_DGRAM:SOCK_STREAM, 0));
}

void mb_socket_ctor2(mb_socket_t s, bool _datagram, int _fd)
{
    memset((void *)&s->in_addr, 0, sizeof(struct sockaddr_in));

    s->datagram = _datagram; 
    s->fd = _fd;

#if defined(FD_CLOEXEC)
    fcntl(s->fd, F_SETFD, FD_CLOEXEC);
#endif

    if (!s->datagram) {
        int one = 1;
        setsockopt(s->fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
    }

    struct timeval tv;
    tv.tv_sec = 100 / 1000;
    tv.tv_usec = (100 % 1000) * 1000;

    setsockopt(s->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
}

void mb_socket_make_sockaddr(const char *address, uint16_t port, struct sockaddr_in *sockaddr)
{
    memset(sockaddr, 0, sizeof(struct sockaddr_in));

    sockaddr->sin_len = sizeof(struct sockaddr_in);
    sockaddr->sin_port = htons(port);
    sockaddr->sin_family = AF_INET;
    sockaddr->sin_addr.s_addr = inet_addr(address);
}

/*
  connect the socket
 */
bool mb_socket_connect(mb_socket_t s, const char *address, uint16_t port)
{
    struct sockaddr_in sockaddr;
    mb_socket_make_sockaddr(address, port, &sockaddr);

    if (connect(s->fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != 0) {
        return false;
    }
    return true;
}

void mb_socket_close(mb_socket_t s)
{
    if (s->fd != -1) {
        close(s->fd);
        s->fd = -1;
    }
}

/*
  bind the socket
 */
bool mb_socket_bind(mb_socket_t s, const char *address, uint16_t port)
{
    struct sockaddr_in sockaddr;
    mb_socket_make_sockaddr(address, port, &sockaddr);

    if (bind(s->fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != 0) {
        return false;
    }
    return true;
}

/*
  set SO_REUSEADDR
 */
bool mb_socket_reuseaddress(mb_socket_const_t s)
{
    int one = 1;
    return (setsockopt(s->fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != -1);
}

/*
  set blocking state
 */
bool mb_socket_set_blocking(mb_socket_const_t s, bool blocking)
{
    int fcntl_ret;
    if (blocking) {
        fcntl_ret = fcntl(s->fd, F_SETFL, fcntl(s->fd, F_GETFL, 0) & ~O_NONBLOCK);
    } else {
        fcntl_ret = fcntl(s->fd, F_SETFL, fcntl(s->fd, F_GETFL, 0) | O_NONBLOCK);
    }
    return fcntl_ret != -1;
}

/*
  set cloexec state
 */
bool mb_socket_set_cloexec(mb_socket_const_t s)
{
#if defined(FD_CLOEXEC)
    return (fcntl(s->fd, F_SETFD, FD_CLOEXEC) != -1);
#else
    return true;
#endif
}

/*
  send some data
 */
ssize_t mb_socket_sendto(mb_socket_t s, const void *buf, size_t size, const char *address, uint16_t port)
{
    struct sockaddr_in sockaddr;
    mb_socket_make_sockaddr(address, port, &sockaddr);
    return sendto(s->fd, buf, size, 0, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
}

/*
  receive some data
 */
ssize_t mb_socket_recv(mb_socket_t s, void *buf, size_t size, uint32_t timeout_ms)
{
    if (!mb_socket_pollin(s, timeout_ms)) {
        return -1;
    }

    socklen_t len = sizeof(s->in_addr);
    return recvfrom(s->fd, buf, size, MSG_DONTWAIT, (struct sockaddr *)&s->in_addr, &len);
}

/*
  return the IP address and port of the last received packet
 */
void mb_socket_last_recv_address(mb_socket_const_t s, const char **ip_addr, uint16_t *port)
{
    *ip_addr = inet_ntoa(s->in_addr.sin_addr);
    *port = ntohs(s->in_addr.sin_port);
}

void mb_socket_set_broadcast(mb_socket_const_t s)
{
    int one = 1;
    setsockopt(s->fd,SOL_SOCKET,SO_BROADCAST,(char *)&one,sizeof(one));
}

/*
  return true if there is pending data for input
 */
bool mb_socket_pollin(mb_socket_t s, uint32_t timeout_ms)
{
    fd_set fds;
    struct timeval tv;

    FD_ZERO(&fds);
    FD_SET(s->fd, &fds);

    tv.tv_sec = timeout_ms / 1000;
    tv.tv_usec = (timeout_ms % 1000) * 1000UL;

    if (select(s->fd+1, &fds, NULL, NULL, &tv) != 1) {
        return false;
    }
    return true;
}

/*
  return true if there is room for output data
 */
bool mb_socket_pollout(mb_socket_t s, uint32_t timeout_ms)
{
    fd_set fds;
    struct timeval tv;

    FD_ZERO(&fds);
    FD_SET(s->fd, &fds);

    tv.tv_sec = timeout_ms / 1000;
    tv.tv_usec = (timeout_ms % 1000) * 1000UL;

    if (select(s->fd+1, NULL, &fds, NULL, &tv) != 1) {
        return false;
    }
    return true;
}

/* 
   start listening for new tcp connections
 */
bool mb_socket_listen(mb_socket_const_t s, uint16_t backlog)
{
    return listen(s->fd, (int)backlog) == 0;
}

/*
  accept a new connection. Only valid for TCP connections after
  listen has been used. A new socket is returned
*/
mb_socket_t mb_socket_accept(mb_socket_t s, uint32_t timeout_ms)
{
    if (!mb_socket_pollin(s, timeout_ms)) {
        return NULL;
    }

    int newfd = accept(s->fd, NULL, NULL);
    if (newfd == -1) {
        return NULL;
    }
    // turn off nagle for lower latency
    int one = 1;
    setsockopt(newfd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));

    mb_socket_t new_mbsocket = (mb_socket_t)rt_malloc(sizeof(struct mb_socket));

    if (new_mbsocket != NULL ) {
        mb_socket_ctor2(new_mbsocket, false, newfd);
    }

    return new_mbsocket;
}

/*------------------------------------test------------------------------------*/
#endif

